"""预测阶段自动输入构建器."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Dict, Tuple

import numpy as np
import pandas as pd

from config.portfolio import get_position_description
from config.settings import get_settings
from src.utils import normalize_symbol
from src.features import build_feature_dataframe
from src.utils import GLOBAL_DATA_STORE


@dataclass(frozen=True)
class PredictionAutoInputs:
    """传递给预测 agent 的基础输入."""

    position: str
    latest_quote: str
    technical_snapshot: str
    model_name: str


class PredictionInputsError(RuntimeError):
    """预测自动输入构建失败."""


def _ensure_dataset(symbol: str):
    dataset = GLOBAL_DATA_STORE.get_dataset(symbol)
    if dataset is None or dataset.frame.empty:
        raise PredictionInputsError(f"缓存中未找到 {symbol} 的历史数据，请先运行风险分析。")
    return dataset


def _ensure_features(symbol: str, frame: pd.DataFrame) -> pd.DataFrame:
    features = GLOBAL_DATA_STORE.get_features(symbol)
    if features is not None and not features.empty:
        return features
    generated = build_feature_dataframe(frame, drop_na=True)
    if generated.empty:
        raise PredictionInputsError("无法基于历史数据生成技术指标。")
    GLOBAL_DATA_STORE.set_features(symbol, generated)
    return generated


def _format_float(value: float, digits: int = 2) -> str:
    return f"{value:.{digits}f}"


def _latest_quote_payload(frame: pd.DataFrame) -> Tuple[pd.Timestamp, Dict[str, float]]:
    last_row = frame.iloc[-1]
    last_index = frame.index[-1]
    payload = {
        "open": float(last_row.get("open", np.nan)),
        "high": float(last_row.get("high", np.nan)),
        "low": float(last_row.get("low", np.nan)),
        "close": float(last_row.get("close", np.nan)),
        "adj_close": float(last_row.get("adj_close", np.nan)),
        "volume": float(last_row.get("volume", np.nan)),
    }
    return last_index, payload


def _format_latest_quote(frame: pd.DataFrame) -> str:
    last_index, payload = _latest_quote_payload(frame)
    close = payload["close"]
    previous_close = frame["close"].iloc[-2] if len(frame) > 1 else np.nan
    change = float(close - previous_close) if not np.isnan(previous_close) else 0.0
    change_pct = (
        (close / previous_close - 1.0) if not np.isnan(previous_close) and previous_close != 0 else 0.0
    )

    volume_text = f"{payload['volume']:,.0f}" if not np.isnan(payload["volume"]) else "N/A"

    return (
        f"日期：{last_index.strftime('%Y-%m-%d')} | "
        f"开盘：{_format_float(payload['open']) if not np.isnan(payload['open']) else 'N/A'} | "
        f"收盘：{_format_float(close) if not np.isnan(close) else 'N/A'} | "
        f"涨跌额：{_format_float(change)} | "
        f"涨跌幅：{change_pct * 100:+.2f}% | "
        f"成交量：{volume_text}"
    )


def _select_feature_values(features: pd.DataFrame) -> Dict[str, float]:
    last_row = features.iloc[-1]
    keys = {
        "10日动量": last_row.get("mom_10"),
        "20日波动率": last_row.get("volatility_20"),
        "SMA20乖离": last_row.get("bias_sma_20"),
        "EMA20乖离": last_row.get("bias_ema_20"),
        "MACD DIF": last_row.get("macd_dif"),
        "MACD DEA": last_row.get("macd_dea"),
        "MACD HIST": last_row.get("macd_hist"),
        "RSI14": last_row.get("rsi_14"),
        "ATR14": last_row.get("atr_14"),
        "布林带位置": last_row.get("bb_position"),
        "布林带宽度": last_row.get("bb_width"),
        "成交量比": last_row.get("volume_ratio"),
    }
    return {k: float(v) if pd.notna(v) else np.nan for k, v in keys.items()}


def _format_indicator(name: str, value: float, percentage: bool = False) -> str:
    if np.isnan(value):
        return f"{name}：数据缺失"
    if percentage:
        return f"{name}：{value * 100:+.2f}%"
    return f"{name}：{value:+.4f}"


def _format_technical_snapshot(features: pd.DataFrame) -> str:
    values = _select_feature_values(features)
    ratio_indicators = {
        "10日动量",
        "20日波动率",
        "SMA20乖离",
        "EMA20乖离",
        "布林带位置",
        "布林带宽度",
    }
    lines = []
    for name, value in values.items():
        if name in {"RSI14", "ATR14"}:
            formatted = f"{name}：{value:.2f}" if not np.isnan(value) else f"{name}：数据缺失"
        elif name in {"MACD DIF", "MACD DEA", "MACD HIST"}:
            formatted = f"{name}：{value:+.4f}" if not np.isnan(value) else f"{name}：数据缺失"
        elif name == "成交量比":
            formatted = f"{name}：{value:.2f}x" if not np.isnan(value) else f"{name}：数据缺失"
        elif name in ratio_indicators:
            formatted = _format_indicator(name, value, percentage=True)
        else:
            formatted = _format_indicator(name, value)
        lines.append(formatted)
    return "\n".join(lines)


def build_prediction_auto_inputs(fund: str) -> PredictionAutoInputs:
    """构建预测所需的自动输入."""

    if not fund:
        raise PredictionInputsError("基金代码不能为空。")

    normalized = normalize_symbol(fund)
    dataset = _ensure_dataset(normalized)
    features = _ensure_features(normalized, dataset.frame)

    latest_quote = _format_latest_quote(dataset.frame)
    technical_snapshot = _format_technical_snapshot(features)

    settings = get_settings()
    model_name = settings.default_model
    position_text = get_position_description(fund)

    return PredictionAutoInputs(
        position=position_text,
        latest_quote=latest_quote,
        technical_snapshot=technical_snapshot,
        model_name=model_name,
    )

